package me.zhengjie.modules.system.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import me.zhengjie.modules.system.domain.FlowConfig;
import me.zhengjie.modules.system.domain.ProcessDefine;
import me.zhengjie.modules.system.service.ProcessDefineService;
import me.zhengjie.utils.FileUtil;
import lombok.RequiredArgsConstructor;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import me.zhengjie.modules.system.service.FlowConfigService;
import me.zhengjie.modules.system.domain.vo.FlowConfigQueryCriteria;
import me.zhengjie.modules.system.mapper.FlowConfigMapper;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.task.api.Task;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import me.zhengjie.utils.PageUtil;

import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.util.ArrayList;
import java.util.LinkedHashMap;

import me.zhengjie.utils.PageResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * @author pysduck
 * @description 服务实现
 * @date 2024-07-08
 **/
@Service
@RequiredArgsConstructor
public class FlowConfigServiceImpl extends ServiceImpl<FlowConfigMapper, FlowConfig> implements FlowConfigService {

    private final FlowConfigMapper flowConfigMapper;
    private final TaskService taskService;
    private final RepositoryService repositoryService;
    private final ProcessDefineService processDefineService;

    @Override
    public PageResult<FlowConfig> queryAll(FlowConfigQueryCriteria criteria, Page<Object> page) {
        return PageUtil.toPage(flowConfigMapper.findAll(criteria, page));
    }

    @Override
    public List<FlowConfig> queryAll(FlowConfigQueryCriteria criteria) {
        return flowConfigMapper.findAll(criteria);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(FlowConfig resources) {
        FlowConfig flowName = baseMapper.selectOne(new QueryWrapper<>(resources).eq("flow_name", resources.getFlowName()));
        if (flowName != null) {
            throw new RuntimeException("流程名已存在");
        }
        resources.setFlowTypeId(resources.getFlowTypes().getId());
        save(resources);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(FlowConfig resources) {
        FlowConfig flowConfig = getById(resources.getId());
        flowConfig.copy(resources);
        saveOrUpdate(flowConfig);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteAll(List<Integer> ids) {
        removeBatchByIds(ids);
    }

    @Override
    public void download(List<FlowConfig> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (FlowConfig flowConfig : all) {
            Map<String, Object> map = new LinkedHashMap<>();
//            map.put("流程名", flowConfig.getFlowName());
//            map.put("流程ID", flowConfig.getFlowId());
//            map.put("流程键", flowConfig.getFlowKey());
//            map.put("创建人", flowConfig.getCreateBy());
//            map.put("更新人", flowConfig.getUpdateBy());
//            map.put("创建时间", flowConfig.getCreateTime());
//            map.put("更新时间", flowConfig.getUpdateTime());
//            map.put("状态", flowConfig.getStatus());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    //设计流程
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void editProcess(FlowConfig resources) {
        FlowConfig flowConfig = getById(resources.getId());
//        if(!flowConfig.getOrginalData().equals(resources.getOrginalData())){
//            flowConfig.setIsDeployed("0");
//        }
        flowConfig.copy(resources);
        saveOrUpdate(flowConfig);

    }

    //部署流程
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deployProcess(FlowConfig resources) throws Exception {
        if ("1".equals(resources.getIsDeployed())) {
            throw new RuntimeException("流程已部署");
        }
        try {
            String bpmnXml = snakerToBpmnXml(resources);
            Deployment deployment = repositoryService.createDeployment().addString(resources.getFlowName() + ".bpmn20.xml", bpmnXml).name(resources.getFlowName()).deploy();
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .deploymentId(deployment.getId())
                    .singleResult();
            resources.setFlowId(processDefinition.getId());
            resources.setIsDeployed("1");
            saveOrUpdate(resources);
            ProcessDefine instance = processDefineService.query().eq("flow_id", resources.getFlowId()).orderByDesc("version").last("LIMIT 1").one();
            ProcessDefine processDefine = new ProcessDefine();
            if (instance == null) {
                processDefine.setVersion(1);
            } else {
                processDefine.setVersion(instance.getVersion() + 1);
            }
            processDefine.setFlowName(resources.getFlowName());
            processDefine.setFlowId(resources.getFlowId());
            processDefine.setFlowTypeId(resources.getFlowTypes().getId());
            processDefine.setIsStart(0);
            processDefine.setOrginaldata(resources.getOrginalData());
            processDefine.setProcessDesignId(resources.getId());
            processDefineService.create(processDefine);
        } catch (Exception e) {
            throw new RuntimeException("部署失败" + e.getMessage());
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Task> getTasks(String assignee) {
        return taskService.createTaskQuery().taskAssigned().list();
    }


    private String snakerToBpmnXml(FlowConfig resource) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        String snakerData = resource.getOrginalData();
        JsonNode rootNode = mapper.readTree(snakerData);
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
        Document doc = docBuilder.newDocument();
        String processId = "process";
        // Create BPMN definitions element
        Element definitions = doc.createElement("definitions");
        definitions.setAttribute("xmlns:flowable", "http://flowable.org/bpmn");
        definitions.setAttribute("xmlns", "http://www.omg.org/spec/BPMN/20100524/MODEL");
        definitions.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        definitions.setAttribute("xmlns:bpmndi", "http://www.omg.org/spec/BPMN/20100524/DI");
        definitions.setAttribute("xmlns:di", "http://www.omg.org/spec/DD/20100524/DI");
        definitions.setAttribute("xmlns:dc", "http://www.omg.org/spec/DD/20100524/DC");
        definitions.setAttribute("xmlns:tns", "http://bpmn.io/schema/bpmn");
        definitions.setAttribute("targetNamespace", "http://bpmn.io/schema/bpmn");
        definitions.setAttribute("id", "Definitions_1");
        doc.appendChild(definitions);

        // Create process element
        Element process = doc.createElement("process");
        process.setAttribute("id", "Process_1");
        process.setAttribute("name", resource.getFlowName());
        process.setAttribute("isExecutable", "true");
        definitions.appendChild(process);

        // Create nodes and edges
        JsonNode nodes = rootNode.get("nodes");
        JsonNode edges = rootNode.get("edges");

        // Store node ids for reference
        for (JsonNode node : nodes) {
            String id = processId + node.get("id").asText();
            String type = node.get("type").asText();
            String name = node.get("text").get("value").asText();
            Element element = null;
            switch (type) {
                case "snaker:start":
                    element = doc.createElement("startEvent");
                    break;
                case "snaker:task":
                    element = doc.createElement("userTask");
                    element.setAttribute("flowable:assignee", "${employee}");
                    break;
                case "snaker:end":
                    element = doc.createElement("endEvent");
                    break;
            }
            if (element != null) {
                element.setAttribute("id", id);
                element.setAttribute("name", name);
                process.appendChild(element);
            }
        }

        // Add sequence flow
        for (JsonNode edge : edges) {
            String id = processId + edge.get("id").asText();
            String sourceId = processId + edge.get("sourceNodeId").asText();
            String targetId = processId + edge.get("targetNodeId").asText();

            Element sequenceFlow = doc.createElement("sequenceFlow");
            sequenceFlow.setAttribute("id", id);
            sequenceFlow.setAttribute("sourceRef", sourceId);
            sequenceFlow.setAttribute("targetRef", targetId);
            process.appendChild(sequenceFlow);
        }

        Element bpmnDiagram = doc.createElement("bpmndi:BPMNDiagram");
        bpmnDiagram.setAttribute("id", "BPMNDiagram_1");
        definitions.appendChild(bpmnDiagram);

        Element bpmnPlane = doc.createElement("bpmndi:BPMNPlane");
        bpmnPlane.setAttribute("id", "BPMNPlane_1");
        bpmnPlane.setAttribute("bpmnElement", "Process_1");
        bpmnDiagram.appendChild(bpmnPlane);

        for (JsonNode node : nodes) {
            String id = processId + node.get("id").asText();
            int x = node.get("x").asInt();
            int y = node.get("y").asInt();

            Element shape = doc.createElement("bpmndi:BPMNShape");
            shape.setAttribute("id", id + "_di");
            shape.setAttribute("bpmnElement", id);

            Element bounds = doc.createElement("dc:Bounds");
            bounds.setAttribute("x", String.valueOf(x));
            bounds.setAttribute("y", String.valueOf(y));
            bounds.setAttribute("width", "100"); // Adjust as necessary
            bounds.setAttribute("height", "80"); // Adjust as necessary
            shape.appendChild(bounds);

            bpmnPlane.appendChild(shape);
        }

        for (JsonNode edge : edges) {
            String id = processId + edge.get("id").asText();
            JsonNode points = edge.get("pointsList");

            Element edgeElement = doc.createElement("bpmndi:BPMNEdge");
            edgeElement.setAttribute("id", id + "_di");
            edgeElement.setAttribute("bpmnElement", id);

            for (JsonNode point : points) {
                int x = point.get("x").asInt();
                int y = point.get("y").asInt();

                Element waypoint = doc.createElement("di:waypoint");
                waypoint.setAttribute("x", String.valueOf(x));
                waypoint.setAttribute("y", String.valueOf(y));
                edgeElement.appendChild(waypoint);
            }

            bpmnPlane.appendChild(edgeElement);
        }

        // Output the XML to a file
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(doc);
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        return writer.toString();
    }


}